﻿/***************************************************************
 * 开发人员：网魂小兵@http://www.mozlite.com
 * 
 * 目标标本：4.0.30319.239
 * 
 * 最后修改：2011/11/1 13:20:38
 * ************************************************************/
namespace Mozlite.Web
{
    using System;
    using System.Linq;
    using System.Text;
    using System.Collections.Generic;
    using System.Web.Mvc;
    using System.Globalization;
    using System.Web;
    using System.IO;
    using Mozlite.Common.Configuration;
    using Mozlite.Common;

    /// <summary>
    /// 模板试图引擎，支持多模板。
    /// </summary>
    public class ThemeViewEngine : BuildManagerViewEngine  
    {
        /// <summary>
        /// 初始化类<see cref="T:Mozlite.Web.ThemeViewEngine"/>。
        /// </summary>
        public ThemeViewEngine()
            : this(null)
        {
        }

        /// <summary>
        /// 初始化类<see cref="T:Mozlite.Web.ThemeViewEngine"/>。
        /// </summary>
        /// <param name="viewPageActivator">对使用依赖项注入实例化视图页的方式进行精细控制。</param>
        public ThemeViewEngine(IViewPageActivator viewPageActivator)
            : base(viewPageActivator)
        {
            AreaViewLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml",
                "~/Areas/{2}/Views/Shared/{0}.cshtml"
            };
            AreaMasterLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml",
                "~/Areas/{2}/Views/Shared/{0}.cshtml"
            };
            AreaPartialViewLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml",
                "~/Areas/{2}/Views/Shared/{0}.cshtml"
            };

            ViewLocationFormats = new[] {
                "~/Themes/{2}/Views/{1}/{0}.cshtml",
                "~/Themes/{2}/Views/Shared/{0}.cshtml"
            };
            MasterLocationFormats = new[] {
                "~/Themes/{2}/Views/{1}/{0}.cshtml",
                "~/Themes/{2}/Views/Shared/{0}.cshtml"
            };
            PartialViewLocationFormats = new[] { 
                "~/Themes/{2}/Views/{1}/{0}.cshtml",
                "~/Themes/{2}/Views/Shared/{0}.cshtml"
            };

            FileExtensions = new[] {
                "cshtml"
            };
        }

        /// <summary>
        /// 使用指定的控制器上下文来创建指定的分部视图。
        /// </summary>
        /// <param name="controllerContext">控制器上下文。</param>
        /// <param name="partialPath">新分部视图的分部路径。</param>
        /// <returns>对分部视图的引用。</returns>
        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return new RazorView(controllerContext, partialPath, null, false, FileExtensions, ViewPageActivator);
        }

        /// <summary>
        /// 使用控制器上下文、视图的路径和母版视图的路径来创建指定视图。
        /// </summary>
        /// <param name="controllerContext">控制器上下文。</param>
        /// <param name="viewPath">视图的路径。</param>
        /// <param name="masterPath">母版视图的路径。</param>
        /// <returns>对视图的引用。</returns>
        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return new RazorView(controllerContext, viewPath, masterPath, true, FileExtensions, ViewPageActivator);
        }

        /// <summary>
        /// 使用指定的控制器上下文和母版视图名称来查找指定的视图。
        /// </summary>
        /// <param name="controllerContext">控制器上下文。</param>
        /// <param name="viewName">视图的名称。</param>
        /// <param name="masterName">母版视图的名称。</param>
        /// <param name="useCache">若为 true，则使用缓存的视图。</param>
        /// <returns>页视图。</returns>
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (string.IsNullOrEmpty(viewName))
            {
                throw new ArgumentException("viewName must be specified.", "viewName");
            }

            string[] searchViewLocations;
            string[] searchMasterLocations;

            string controller = controllerContext.RouteData.GetRequiredString("controller");
            string viewPath;
            string masterPath;
            if (controllerContext.IsAreaContext())
            {
                string areaName = GetAreaTheme(controllerContext);
                viewPath = this.GetPath(controllerContext, this.AreaViewLocationFormats, "AreaViewLocationFormats",
                        viewName, areaName, controller, "View", useCache, out searchViewLocations);

                masterPath = this.GetPath(controllerContext, this.AreaMasterLocationFormats, "AreaMasterLocationFormats",
                        masterName, areaName, controller, "Master", useCache, out searchMasterLocations);
            }
            else
            {
                viewPath = this.GetPath(controllerContext, this.ViewLocationFormats, "ViewLocationFormats",
                        viewName, GetAreaTheme(controllerContext), controller, "View", useCache, out searchViewLocations);

                masterPath = this.GetPath(controllerContext, this.MasterLocationFormats, "MasterLocationFormats",
                        masterName, GetAreaTheme(controllerContext), controller, "Master", useCache, out searchMasterLocations);
            }
            if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName)))
            {
                return new ViewEngineResult(this.CreateView(controllerContext, viewPath, masterPath), this);
            }
            if (searchMasterLocations != null && searchMasterLocations.Length > 0)
                return new ViewEngineResult(searchViewLocations.Union<string>(searchMasterLocations));
            return new ViewEngineResult(searchViewLocations);
        }

        /// <summary>
        ///  使用指定的控制器上下文查找指定的分部视图。
        /// </summary>
        /// <param name="controllerContext">控制器上下文。</param>
        /// <param name="viewName">分部视图的名称。</param>
        /// <param name="useCache">若为 true，则使用缓存的分部视图。</param>
        /// <returns>分部视图。</returns>
        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (string.IsNullOrEmpty(partialViewName))
            {
                throw new ArgumentException("partialViewName must be specified.", "partialViewName");
            }

            string[] searchLocations;
            string controller = controllerContext.RouteData.GetRequiredString("controller");
            string partialViewPath;

            if (controllerContext.IsAreaContext())
            {
                partialViewPath = this.GetPath(controllerContext, this.AreaPartialViewLocationFormats,
                        "AreaPartialViewLocationFormats", partialViewName, GetAreaTheme(controllerContext), controller, "Partial", useCache, out searchLocations);
            }
            else
            {
                partialViewPath = this.GetPath(controllerContext, this.PartialViewLocationFormats,
                        "PartialViewLocationFormats", partialViewName, GetAreaTheme(controllerContext), controller, "Partial", useCache, out searchLocations);
            }
            if (string.IsNullOrEmpty(partialViewPath))
            {
                return new ViewEngineResult(searchLocations);
            }
            return new ViewEngineResult(this.CreatePartialView(controllerContext, partialViewPath), this);

        }

        #region helpers
        private string GetAreaTheme(ControllerContext controllerContext)
        {
            if (controllerContext.IsAreaContext())
            {
                string areaName = controllerContext.GetAreaName();
                ApplicationBase application = Applications.GetApplication(areaName);
                if (application == null || !application.IsThemable)
                    return areaName;
                string theme = "default";
                var configuration = application.GetConfiguration();
                if (configuration != null)
                    theme = configuration.GetTheme(controllerContext);
                return string.Format("{0}/Themes/{1}", areaName, theme);
            }
            return Application.Current.Configuration.GetTheme(controllerContext);
        }

        private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
        {
            searchedLocations = null;
            if (string.IsNullOrEmpty(name))
            {
                return string.Empty;
            }
            if (locations == null || locations.Length == 0)
            {
                throw new InvalidOperationException("locations must not be null or emtpy.");
            }

            bool flag = IsSpecificPath(name);
            string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
            if (useCache)
            {
                string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
                if (viewLocation != null)
                {
                    return viewLocation;
                }
            }
            if (!flag)
            {
                return this.GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations);
            }
            return this.GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
        }

        private static bool IsSpecificPath(string name)
        {
            char ch = name[0];
            if (ch != '~')
            {
                return (ch == '/');
            }
            return true;
        }

        private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
        {
            return string.Format(CultureInfo.InvariantCulture, ":ViewCacheKey:{0}:{1}:{2}:{3}:{4}", new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName });
        }

        private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
        {
            string virtualPath = string.Empty;
            searchedLocations = new string[locations.Length];
            bool isExists = false;
            for (int i = 0; i < locations.Length; i++)
            {
                string location = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, themeName });
                isExists = this.FileExists(controllerContext, location);
                if (!isExists&&!controllerContext.IsAreaContext()) {
                    location = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, SiteSettings.Default.Theme });
                    isExists = this.FileExists(controllerContext, location);
                }
                if (isExists)
                {
                    searchedLocations = null;
                    virtualPath = location;
                    this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                    return virtualPath;
                }
                searchedLocations[i] = location;
            }
            return virtualPath;
        }

        private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
        {
            string virtualPath = name;
            if (!this.FileExists(controllerContext, name))
            {
                virtualPath = string.Empty;
                searchedLocations = new string[] { name };
            }
            this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
            return virtualPath;
        }
        #endregion
    }
}